Skip to content

feat: add Trainer control-plane metrics - #4190

Draft
abhijeet-dhumal wants to merge 4 commits into
kubeflow:masterfrom
abhijeet-dhumal:trainer-metrics-implementation
Draft

abhijeet-dhumal wants to merge 4 commits into
kubeflow:masterfrom
abhijeet-dhumal:trainer-metrics-implementation

Conversation

@abhijeet-dhumal

@abhijeet-dhumal abhijeet-dhumal commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

What this PR does / why we need it

Adds six upstream Trainer Prometheus metrics for runtime inventory, TrainJob creation, completion, failure, lifetime, and requested training-node counts proposed in #3429:

Metrics are registered only by the elected controller leader and collected from informer-backed snapshots without Kubernetes API calls during scraping. The implementation also handles duplicate terminal events, runtime updates/deletions, and leader transitions.

TLS, authentication, and ServiceMonitor configuration are handled separately by PR #4102.

  • kubeflow_trainer_runtime_info: current TrainingRuntime and ClusterTrainingRuntime inventory.
  • kubeflow_trainer_trainjob_created_total: TrainJobs observed after the initial informer cache listing.
  • kubeflow_trainer_trainjob_finished_total: terminal outcomes by result, reason, and runtime.
  • kubeflow_trainer_trainjob_lifetime_seconds: time from TrainJob creation to terminal state.
  • kubeflow_trainer_trainjob_requested_training_nodes_per_job: requested training-node size.

The controller uses its existing informer caches instead of calling the Kubernetes API during metric collection. Runtime information is kept in an in-memory snapshot and reused when processing TrainJobs.
Only the elected controller leader processes metric events and registers the Trainer metrics. When leadership changes, the old leader removes its metrics and the new leader registers them. This prevents duplicate metrics from multiple controller replicas.

The implementation also:

  • avoids counting TrainJobs already present when the cache starts;
  • records terminal metrics only once per TrainJob;
  • avoids high-cardinality labels such as TrainJob names and UIDs;
  • includes unit and race-test coverage;
  • documents metric behavior, PromQL examples, and limitations.

Validation

  • Unit tests for runtime inventory, TrainJob events, runtime policy resolution, terminal-state handling, and metric lifecycle.
  • Race tests for the metrics package.
  • go vet ./pkg/metrics/...
  • Formatting and git diff --check.
  • Existing repository code-quality, generation, and language test checks.
  • Prometheus metrics architecture and monitoring documentation.

Follow-up

Final CI, documentation build, approver review, and cluster E2E validation remain required before merge.

Checklist

  • Docs included for the user-facing metrics and monitoring changes
  • Unit tests added
  • Leader ownership and registration lifecycle covered

@google-oss-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign tenzen-y for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@abhijeet-dhumal
abhijeet-dhumal marked this pull request as ready for review October 7, 2026 12:44
@google-oss-prow
google-oss-prow Bot requested a review from kuizhiqing October 7, 2026 12:44

@robert-bell robert-bell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @abhijeet-dhumal! I've left some inline feedback.

Comment thread pkg/metrics/setup.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/instrumentation_test.go Resolved
@robert-bell robert-bell mentioned this pull request Oct 7, 2026
8 tasks done
@abhijeet-dhumal
abhijeet-dhumal force-pushed the trainer-metrics-implementation branch from 71adb35 to 9b58b90 Compare October 8, 2026 08:53
Comment thread cmd/trainer-controller-manager/main.go Resolved
Comment thread pkg/metrics/setup.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
@abhijeet-dhumal

Copy link
Copy Markdown
Member Author

Thanks @robert-bell .. I’ve tried to address the metric scope and test coverage updates.

Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/trainer_metrics.go Resolved
Comment thread pkg/metrics/instrumentation_test.go Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread pkg/metrics/metrics.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/metrics.go Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread docs/operator-guides/metrics.md Resolved
@abhijeet-dhumal
abhijeet-dhumal force-pushed the trainer-metrics-implementation branch 2 times, most recently from 1ad4f92 to 0f47c2d Compare October 14, 2026 12:41
@abhijeet-dhumal
abhijeet-dhumal marked this pull request as ready for review October 14, 2026 13:48

@andreyvelich andreyvelich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @abhijeet-dhumal for the work on this! Review done using AI tools; inline comments below.

Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation_test.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/metrics.go Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Signed-off-by: abhijeet-dhumal <abhijeetdhumal652@gmail.com>
@andreyvelich

Copy link
Copy Markdown
Member

/milestone v2.4

@google-oss-prow google-oss-prow Bot added this to the v2.4 milestone Oct 21, 2026

@robert-bell robert-bell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @abhijeet-dhumal this is looking pretty close!

Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread pkg/metrics/instrumentation.go Resolved
Comment thread docs/operator-guides/index.md Resolved
Comment thread docs/operator-guides/metrics.md Resolved
Comment thread pkg/metrics/metrics.go Resolved
Comment thread pkg/metrics/metrics.go Resolved
Comment thread pkg/metrics/metrics.go
}, []string{"result", "runtime_ref"})
trainJobRequestedTrainingNodes = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: metricNamespace,
Name: "trainjob_requested_training_nodes_per_job",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to add this in the phase 1, right @robert-bell @abhijeet-dhumal ?

Suggested change
Name: "trainjob_requested_training_nodes_per_job",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be keen to keep this in phase 1. It gives admins info about job sizes which is useful for investing in optimising network configs.

Comment on lines +64 to +66
log: ctrl.Log.WithName("trainer-metrics"),
runtimeInfo: newRuntimeInfoCollector(c),
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a separate informer handler @abhijeet-dhumal @robert-bell?
In JobSet, we directly call the metrics counter from the controller loop: https://github.com/kubernetes-sigs/jobset/blob/main/pkg/controllers/failure_policy.go#L432

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the informer completely decouples the metrics instrumentation from the reconciliation logic keeping both clean and easier to evolve independently.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we prefer approach to decouple it?

Would love to hear feedback from the Kueue maintainer on this too @kannon92 @tenzen-y!
https://github.com/kubernetes-sigs/kueue/tree/main/pkg/metrics

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer keeping the informer-based observer... it keeps metrics independent of reconciliation and records lifecycle events without adding metrics work to the reconcile path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abhijeet-dhumal we talked about this today at the Trainer call with @robert-bell and @tenzen-y.
Ref: https://youtu.be/nsE0a4T9LFM?t=959

Given the trade-offs of using separate watchers for events, could we explore how other Kubernetes projects handle event processing, particularly since our current implementation differs from Kueue and Training Operator v1?

My main concern with your proposed approach is that metric updates would rely solely on eventual consistency, as the metrics might not be updated at the time the corresponding event is processed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @andreyvelich, @tenzen-y, I've done an investigation into this.

I looked back at the call and I think there were 2 points we wanted to clarify:

  1. What's the existing precedent for using the informer-cache pattern, rather than instrumenting the reconcile loop like kueue and Training Operator v1?
  2. Are there any problems with the eventual consistency of the informer-backed metrics? Yuki's concern was that metrics recorded at cache-event time rather than controller-action time would diverge from the controller logs, making it harder for admins to correlate the two when debugging production issues.

How other projects do it.
The pattern in this PR has more precedent than the Kueue/Training Operator v1 comparison suggests:

  • Kueue records finished_workloads_total from a watch event handler on the shared informer cache, with old/new transition guards (workload_controller.go, handleUpdate) -- structurally the same approach as this PR's terminal counters. Its scheduler-decision metrics are recorded at the decision point, which is the other category.
  • Several widely-deployed projects derive object-state metrics from informer-backed caches, decoupled from reconcile loops:
  • JobSet and Training Operator v1 do count in the reconcile loop, and JobSet's own code comments document working around retry-driven "metric inflation" in that design.

The distinction that falls out is state-derived vs action-derived metrics. All the metrics in this PR are state metrics (what exists / what happened in the cluster), which is what the informer pattern is for; controller-behaviour metrics are already exported by controller-runtime's built-in reconcile_* family.

Eventual consistency in practice.
This PR is using the same cache as the main reconciliation loop, so there's no realistic divergence from what the reconcile loop would see. I did measure latency locally on a kind cluster which confirms the delay is minimal - e.g. trainjob_failed_total incremented within ~14 ms of an independent watch stream observing the terminal condition -- in some trials before my own watcher received the event.

@tenzen-y on your log-correlation concern: counter samples are timestamped at scrape time, so no user can localize a counter increment finer than the scrape interval (e.g. 30 s default) regardless of where the increment happens -- moving it into the reconcile loop would not change observable timing.

Recommendation
I think we should be good to go ahead with the implementation as-is -- though we can add an alpha feature gate for it if you'd prefer.

We do need to update docs/operator-guides/metrics.md to mention the metric collections are best effort. @abhijeet-dhumal could you add that to this PR when you get a chance? I'll add some suggestions.

For transparency, I used AI to help me with the investigation, but I've manually reviewed and edited the information.

Signed-off-by: abhijeet-dhumal <abhijeetdhumal652@gmail.com>
Signed-off-by: abhijeet-dhumal <abhijeetdhumal652@gmail.com>
Signed-off-by: abhijeet-dhumal <abhijeetdhumal652@gmail.com>
@abhijeet-dhumal

Copy link
Copy Markdown
Member Author

@andreyvelich I’ve tried to address the review comments in the latest changes, including the documentation updates. Could you please take another look when you have a chance and let me know if anything else needs work?

@andreyvelich

Copy link
Copy Markdown
Member

/milestone clear

@google-oss-prow google-oss-prow Bot removed this from the v2.4 milestone Oct 30, 2026
@abhijeet-dhumal
abhijeet-dhumal marked this pull request as draft November 12, 2026 07:27
@abhijeet-dhumal

Copy link
Copy Markdown
Member Author

Need to discuss/align and sign off on structural LLD for this implementation!
cc: @robert-bell

Comment on lines +29 to +31
- Creation, completion, and workload metrics are event-based and reset when
the controller process restarts or leadership changes. Existing objects
found during the initial informer listing are not counted as newly created.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could add this clarification to help users understand the limitation -

Suggested change
- Creation, completion, and workload metrics are event-based and reset when
the controller process restarts or leadership changes. Existing objects
found during the initial informer listing are not counted as newly created.
- Creation, completion, and workload metrics are event-based and reset when
the controller process restarts or leadership changes. Existing objects
found during the initial informer listing are not counted as newly created.
As a consequence, TrainJobs created while no leader replica is running
(for example, during a leadership transition or controller downtime) are
not counted by `kubeflow_trainer_trainjob_created_total`.

- Labels use normalized identifiers and bounded categories, but total
cardinality still grows with the number of runtimes and namespaces. Avoid
adding job names, UIDs, raw errors, or other unbounded values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we add this clarification

Suggested change
- The TrainJob metrics are updated by event handlers on the same informer cache
that drives reconciliation, so they are eventually consistent with the API
server: increments appear shortly after the corresponding object change,
with a delay that is small relative to a typical scrape interval.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

3 participants